#include "api.h"
#include "basics/error_handle.h"
#include "basics/macros.h"
#include <stdbool.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>

static int send_req(int socket, enum API_ID id) {
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCK_NAME);
    if_fail_return(
        connect(socket, (struct sockaddr *)&server_addr, sizeof(server_addr)));
    if_fail_return(send(socket, &id, sizeof(id), 0));
    /// whatever you get, it indicates the send successed.
    char buf[1];
    if_fail_return(recv(socket, buf, sizeof(buf), 0));
    return 0;
}

int create(const char *image_path, const char *command) {
    int client = socket(AF_UNIX, SOCK_STREAM, 0);
    check(client != -1);
    check(send_req(client, CNTR_CREATE) != -1);
    // snprintf(buf, BUFF_MAX, "%d %d %lu %s %lu %s", getuid(), getgid(),
    //          strlen(image_path), image_path, strlen(command), command);
    uid_t uid = getuid();
    gid_t gid = getgid();
    size_t image_path_len = strlen(image_path) + 1;
    size_t command_len = strlen(command) + 1;
    check(send(client, &uid, sizeof(uid_t), 0) != -1);
    check(send(client, &gid, sizeof(gid_t), 0) != -1);
    check(send(client, &image_path_len, sizeof(size_t), 0) != -1);
    check(send(client, image_path, image_path_len, 0) != -1);
    check(send(client, &command_len, sizeof(size_t), 0) != -1);
    check(send(client, command, command_len, 0) != -1);
    int container_id;
    check(recv(client, &container_id, sizeof(int), 0) != -1);
    check(close(client) != -1);
    return container_id;
}

int attach(const int container_id, const char *command) {
    int client = socket(AF_UNIX, SOCK_STREAM, 0);
    check(client != -1);
    check(send_req(client, CNTR_ATTACH) != -1);
    uid_t uid = getuid();
    gid_t gid = getgid();
    pid_t pgid = getpgid(0);
    char *tty = ttyname(STDIN_FILENO);
    printf("ttyname = %s\n", tty);
    size_t tty_len = strlen(tty) + 1;
    size_t command_len = strlen(command) + 1;
    check(send(client, &uid, sizeof(uid_t), 0) != -1);
    check(send(client, &gid, sizeof(gid_t), 0) != -1);
    check(send(client, &pgid, sizeof(pid_t), 0) != -1);
    check(send(client, &tty_len, sizeof(size_t), 0) != -1);
    check(send(client, tty, tty_len, 0) != -1);
    check(send(client, &container_id, sizeof(int), 0) != -1);
    check(send(client, &command_len, sizeof(size_t), 0) != -1);
    check(send(client, command, command_len, 0) != -1);
    /// temporarily stop stdin, stdout and stderr for this fg process
    int dup_stdin = dup(STDIN_FILENO);
    check(dup_stdin != -1);
    int dup_stdout = dup(STDOUT_FILENO);
    check(dup_stdout != -1);
    int dup_stderr = dup(STDERR_FILENO);
    check(dup_stderr != -1);
    /// don't check this ioctl, if it fails, continue to run
    // ioctl(STDIN_FILENO, TIOCNOTTY);
    check(close(STDIN_FILENO) != -1);
    check(close(STDOUT_FILENO) != -1);
    check(close(STDERR_FILENO) != -1);
    /// block the server untill foreground client is finnally on the background
    int rt;
    check(send(client, &rt, sizeof(int), 0) != -1);
    /// this will be blocked
    check(recv(client, &rt, sizeof(int), MSG_WAITALL) != -1);
    check(close(client) != -1);
    check(dup2(dup_stdin, STDIN_FILENO) != -1);
    check(dup2(dup_stdout, STDOUT_FILENO) != -1);
    check(dup2(dup_stderr, STDERR_FILENO) != -1);
    check(close(dup_stdin) != -1);
    check(close(dup_stdout) != -1);
    check(close(dup_stderr) != -1);
    /// small tricks to avoid process id being changed to set control tty
    // pid_t dummy_child = fork();
    // if (dummy_child == 0) {
    //   check(setsid() != -1);
    //   check(ioctl(STDIN_FILENO, TIOCSCTTY, 0) != -1);
    // } else if (dummy_child > 0) {
    //   exit(0);
    // } else {
    //   check(dummy_child >= 0);
    // }
    return rt;
}

int delete(const int container_id) {
    int client = socket(AF_UNIX, SOCK_STREAM, 0);
    check(client != -1);
    check(send_req(client, CNTR_DELETE) != -1);
    check(send(client, &container_id, sizeof(int), 0) != -1);
    int rt;
    check(recv(client, &rt, sizeof(int), 0) != -1);
    check(close(client) != -1);
    return rt;
}

int update(const int container_id, const char update_type,
           const unsigned int update_value) {
    int client = socket(AF_UNIX, SOCK_STREAM, 0);
    check(client != -1);
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCK_NAME);
    check(connect(client, (struct sockaddr *)&server_addr,
                  sizeof(server_addr)) != -1);
    enum API_ID id = CNTR_UPDATE;
    check(send(client, &id, sizeof(id), 0) != -1);
    /// whatever you get, it indicates the send successed.
    char buf[1];
    check(recv(client, buf, sizeof(buf), 0) != -1);
    check(send(client, &container_id, sizeof(int), 0) != -1);
    check(send(client, &update_type, sizeof(char), 0) != -1);
    check(send(client, &update_value, sizeof(unsigned int), 0) != -1);

    int rt;
    check(recv(client, &rt, sizeof(int), 0) != -1);
    check(close(client) != -1);
    return rt;
}
